CloudFormationの中のEC2のユーザーデータでシェル変数を使用する
CloudFormationでEC2のユーザーデータをごりっごりに書きたいんじゃ…!
そんな気分の時はございませんか?
ユーザーデータとは、EC2起動時に実行されるシェルスクリプトです。
Linux インスタンスでの起動時のコマンドの実行 - Amazon Elastic Compute Cloud
起動時にホスト名、時刻同期、文字コード、タイムゾーンの設定をスクリプト化することでEC2の構築作業を省力化できます。 弊社ブログで具体的な方法を紹介しています。
[AWS]CloudFormationを使いこなして早く帰るTips5選 | DevelopersIO
そんな中、ユーザーデータでシェル変数を取り扱う時にちょっと詰まったのでその備忘録です。
CloudFormationでユーザーデータを書く
ユーザーデータをEC2に直接定義するとマネジメントコンソールで確認できないため、今回はLaunchTemplate(起動テンプレート)を利用します。
CloudFormationでユーザーデータを定義したLaunchTemplateを書くとこんな感じです。 ホームフォルダによくわかんないファイルを作っていますが、とりあえず流してください。
--- AWSTemplateFormatVersion: '2010-09-09' Resources: SampleLaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: sample-launch-template LaunchTemplateData: UserData: Fn::Base64: | #!/bin/bash echo INSTANCE_ID > /home/ec2-user/instance-id.txt
CloudFormationでユーザーデータを記述する場合、Base64エンコードされてなければいけません。なので組み込み関数の Fn::Base64
を使用しています。
AWS::EC2::Instance - AWS CloudFormation
Fn::Base64 - AWS CloudFormation
組み込み関数Fn::Subを使用する
ユーザーデータの中でCloudFormationのパラメーターを使いたいことがよくあります。
ホスト名をパラメーターで指定できるようにしてテンプレートを使いまわしたいとか、そういうケースです。
そういう場合は、組み込み関数Fn::Subを使用します。
ファイル名をパラメータ指定できるように変更するとこんな感じです。
AWSTemplateFormatVersion: '2010-09-09' Parameters: InstanceIdFileName: Type: String Default: instance-id.txt Resources: SampleLaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: sample-launch-template LaunchTemplateData: UserData: Fn::Base64: !Sub - | #!/bin/bash echo INSTANCE_ID > /home/ec2-user/${FileName} - { FileName: !Ref InstanceIdFileName }
ユーザーデータの中でシェル変数を使用する
さて、ここからが本題です。
Fn::Subの変数は ${MyVarName}
といった表記をしているんですが、完全にシェルの変数参照と表記が被っています。
なのでcurlコマンド使ってEC2のメタデータからインスタンスIDとってきて変数に格納、後の行で変数参照しようと思って以下の様な書き方をすると、 Unresolved resource dependencies [INSTANCE_ID]
ってvalidateで怒られます。
--- AWSTemplateFormatVersion: '2010-09-09' Parameters: InstanceIdFileName: Type: String Default: instance-id.txt Resources: SampleLaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: sample-launch-template LaunchTemplateData: UserData: Fn::Base64: !Sub - | #!/bin/bash INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) echo ${INSTANCE_ID} > /home/ec2-user/${FileName} - { FileName: !Ref InstanceIdFileName }
どうしたものかと悩んでいたんですが、よく見るとエスケープの方法がドキュメントに記載されていました。
ドル記号と中括弧 (${}) をそのまま書き込むには、最初の中括弧の後に感嘆符 (!) を追加します (${!Literal} など)。AWS CloudFormation では、このテキストは ${Literal} のようになります。
と、いうわけでドキュメントに記載されている通りエスケープしてやれば、シェル変数もガンガン使えます。
--- AWSTemplateFormatVersion: '2010-09-09' Parameters: InstanceIdFileName: Type: String Default: instance-id.txt Resources: SampleLaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: sample-launch-template LaunchTemplateData: UserData: Fn::Base64: !Sub - | #!/bin/bash INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) echo ${!INSTANCE_ID} > /home/ec2-user/${FileName} - { FileName: !Ref InstanceIdFileName }
実際にCloudFormationスタックを作成して、マネジメントコンソールでユーザーデータの内容を確認してみると、ファイル名だけCloudFormationのパラメーターに置換されているのに対して、エスケープした ${INSTANCE_ID}
はそのまま残せています。
このLaunchTemplateを元にしてEC2を立ててSSH接続してみると、想定したとおりホームフォルダにインスタンスIDが記載されたファイルができていることがわかります。
おわりに
これでユーザーデータに変数山盛りにしたCloudFormationテンプレートがガンガン書けますね!!
他の人が読めるテンプレートになるよう、ほどほどにしておきましょう。
おまけ
検証に使用したEC2付きのCloudFormationテンプレートを記載しておきます。 イメージID,キーペア名,セキュリティグループID,サブネットIDは適切に設定してください。
--- AWSTemplateFormatVersion: '2010-09-09' Parameters: InstanceIdFileName: Type: String Default: instance-id.txt Ec2InstanceType: Type: String Default: t2.micro Ec2ImageId: Type: AWS::EC2::Image::Id Ec2KeyPairName: Type: AWS::EC2::KeyPair::KeyName Ec2SecurityGroupId: Type: AWS::EC2::SecurityGroup::Id Ec2SubnetId: Type: AWS::EC2::Subnet::Id Resources: SampleLaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: sample-launch-template LaunchTemplateData: UserData: Fn::Base64: !Sub - | #!/bin/bash INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) echo ${!INSTANCE_ID} > /home/ec2-user/${FileName} - { FileName: !Ref InstanceIdFileName } SampleInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref Ec2ImageId InstanceType: !Ref Ec2InstanceType KeyName: !Ref Ec2KeyPairName SecurityGroupIds: - !Ref Ec2SecurityGroupId SubnetId: !Ref Ec2SubnetId Tags: - Key: Name Value: sample-ec2 LaunchTemplate: LaunchTemplateId: !Ref SampleLaunchTemplate Version: !GetAtt SampleLaunchTemplate.LatestVersionNumber